using DevExpress.Blazor;
using DevExpress.Data.Filtering;
using DevExpress.Data.Filtering.Helpers;
using DevExpress.ExpressApp.Blazor.Components;
using DevExpress.ExpressApp.Blazor.Components.Models;
using DevExpress.ExpressApp.Blazor.Services;
using EasyXaf.DynamicForm.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System.Dynamic;

namespace EasyXaf.DynamicForm.Blazor.Components;

public class FormComponent : ComponentBase
{
    private IDictionary<string, object> FormDataDict => FormData;

    [Parameter]
    public Form Form { get; set; }

    [Parameter]
    public ExpandoObject FormData { get; set; } = new();

    [Parameter]
    public EventCallback<ExpandoObject> FormDataChanged { get; set; }

    [Inject]
    private IImageUrlService ImageUrlService { get; set; }

    private static int ColSpanMd(FormItem formItem)
    {
        if (formItem.Parent is Panel panel && panel.Direction == FlowDirection.Horizontal)
        {
            if (formItem.Width.HasValue && formItem.Width.Value > 0)
            {
                return (int)Math.Round(formItem.Width.Value * 0.12);
            }
            else
            {
                return Math.Max((int)Math.Round(12d / panel.Items.Count(item => item.ActualVisible)), 1);
            }
        }
        return 12;
    }

    private bool CheckElementReadOnly(Element element, ExpandoObject data = null)
    {
        var @readonly = element.ReadOnly?.Trim();

        if (!string.IsNullOrWhiteSpace(@readonly))
        {
            if (bool.TryParse(@readonly, out var value))
            {
                return value;
            }

            if (@readonly.StartsWith('{') && @readonly.EndsWith('}'))
            {
                var propertyDescriptors = (data ?? FormData).ToPropertyDescriptorCollection();
                var expression = @readonly.TrimStart('{').TrimEnd('}');
                var expressionEvaluator = new ExpressionEvaluator(propertyDescriptors, expression);
                return expressionEvaluator.Fit(FormData);
            }
        }

        return false;
    }

    private bool CheckElementVisibility(Element element, ExpandoObject data = null)
    {
        var visible = element.Visible?.Trim();

        if (!string.IsNullOrWhiteSpace(visible))
        {
            if (bool.TryParse(visible, out var value))
            {
                return value;
            }

            if (visible.StartsWith('{') && visible.EndsWith('}'))
            {
                var propertyDescriptors = (data ?? FormData).ToPropertyDescriptorCollection();
                var expression = visible.TrimStart('{').TrimEnd('}');
                var expressionEvaluator = new ExpressionEvaluator(propertyDescriptors, expression);
                return expressionEvaluator.Fit(FormData);
            }
        }

        return true;
    }

    private void UpdatePanelStatus(Panel panel)
    {
        panel.ActualReadOnly = CheckElementReadOnly(panel);
        panel.ActualVisible = CheckElementVisibility(panel);

        foreach (var item in panel.Items)
        {
            if (item is Panel panel2)
            {
                panel2.ActualReadOnly = CheckElementReadOnly(panel2);
                panel2.ActualVisible = CheckElementVisibility(panel2);

                UpdatePanelStatus(panel2);
            }
            else if (item is Tabs tabs)
            {
                tabs.ActualReadOnly = CheckElementReadOnly(tabs);
                tabs.ActualVisible = CheckElementVisibility(tabs);

                foreach (var tab in tabs.TabPanels)
                {
                    tab.ActualReadOnly = CheckElementReadOnly(tab);
                    tab.ActualVisible = CheckElementVisibility(tab);

                    UpdatePanelStatus(tab);
                }
            }
            else if (item is Field field)
            {
                field.ActualReadOnly = CheckElementReadOnly(field);
                field.ActualVisible = CheckElementVisibility(field);

                if (field.IsCalculated)
                {
                    var value = EvaluateFieldExpression(field, FormData);
                    field.ActualValue = value;
                    FormDataDict[field.Name] = value;
                }

                if (field is Table table)
                {
                    foreach (var column in table.Columns)
                    {
                        column.ActualReadOnly = CheckElementReadOnly(column);
                        column.ActualVisible = CheckElementVisibility(column);
                    }

                    EvaluateTableExpression(table, FormData);
                }
            }
        }
    }

    private void UpdateFormStatus()
    {
        UpdatePanelStatus(Form);
    }

    private object EvaluateFieldExpression(Field field, ExpandoObject data)
    {
        var criteria = CriteriaOperator.Parse(field.Value.TrimStart('{').TrimEnd('}'));
        var propertyDescriptors = data.ToPropertyDescriptorCollection();

        var criteriaProcessor = new CriteriaConverterProcessor();
        criteria.Accept(criteriaProcessor);

        if (criteriaProcessor.NamedParameters.Count > 0)
        {
            var parameters = new List<object>();
            var formPropertyDescriptors = FormData.ToPropertyDescriptorCollection();
            var criteriaText = criteria.ToString();
            foreach (var parameter in criteriaProcessor.NamedParameters)
            {
                criteriaText = criteriaText.Replace($"'{parameter.Key}'", "?");
                parameters.Add(new ExpressionEvaluator(formPropertyDescriptors, parameter.Value).Evaluate(FormData));
            }
            criteria = CriteriaOperator.Parse(criteriaText, [.. parameters]);
        }

        var expressionEvaluator = new ExpressionEvaluator(propertyDescriptors, criteria);
        return expressionEvaluator.Evaluate(data);
    }

    private List<ExpandoObject> EvaluateTableExpression(Table table, ExpandoObject data)
    {
        var dict = (IDictionary<string, object>)data;
        if (dict.TryGetValue(table.Name, out var value))
        {
            var expandoObjects = value as List<ExpandoObject>;
            if (expandoObjects == null && value is List<object> objects)
            {
                expandoObjects = objects.OfType<ExpandoObject>().ToList();
            }

            if (expandoObjects != null)
            {
                foreach (var column in table.Columns)
                {
                    if (column.IsCalculated)
                    {
                        foreach (var expandoObject in expandoObjects)
                        {
                            var fieldValue = EvaluateFieldExpression(column, expandoObject);
                            ((IDictionary<string, object>)expandoObject)[column.Name] = fieldValue;
                        }
                    }
                }
                return expandoObjects;
            }
        }
        return [];
    }

    private RenderFragment CreateFormItems(IEnumerable<FormItem> items)
    {
        return builder =>
        {
            int sequence = 0;

            foreach (var item in items)
            {
                if (item is Panel panel)
                {
                    builder.AddContent(sequence++, CreatePanel(panel));
                }
                else if (item is Tabs tabs)
                {
                    builder.AddContent(sequence++, CreateTabs(tabs));
                }
                else if (item is Field field)
                {
                    builder.AddContent(sequence++, CreateField(field));
                }
            }
        };
    }

    private RenderFragment CreateTabPanel(TabPanel tabPanel)
    {
        var model = new DxFormLayoutTabPageModel
        {
            Id = tabPanel.Id,
            Caption = tabPanel.Caption,
            ColSpanMd = ColSpanMd(tabPanel),
            ReadOnly = tabPanel.IsCurrentlyReadOnly,
            Visible = tabPanel.ActualVisible,
            Items = CreateFormItems(tabPanel.Items)
        };

        if (!string.IsNullOrWhiteSpace(tabPanel.ImageName))
        {
            model.HeaderIconCssClass = "xaf-layout-tab-icon";
            model.HeaderIconUrl = ImageUrlService.GetImageUrl(tabPanel.ImageName);
        }

        return ComponentModelObserver.Create(model, model.GetComponentContent());
    }

    private RenderFragment CreateTabs(Tabs tabs)
    {
        var model = new DxFormLayoutTabPagesModel
        {
            Id = tabs.Id,
            RenderMode = TabsRenderMode.AllTabs,
            ScrollMode = TabsScrollMode.Auto,
            ColSpanMd = ColSpanMd(tabs),
            ReadOnly = tabs.IsCurrentlyReadOnly,
            Visible = tabs.ActualVisible,
            ActiveTabIndex = tabs.ActiveTabIndex,
            ActiveTabIndexChanged = EventCallback.Factory.Create<int>(this, index =>
            {
                tabs.ActiveTabIndex = index;
            }),
            Items = builder =>
            {
                int sequence = 0;
                foreach (var tabPanel in tabs.TabPanels)
                {
                    builder.AddContent(sequence++, CreateTabPanel(tabPanel));
                }
            }
        };

        return ComponentModelObserver.Create(model, model.GetComponentContent());
    }

    private RenderFragment CreatePanel(Panel panel)
    {
        var model = new DxFormLayoutGroupModel
        {
            Id = panel.Id,
            Caption = panel.Caption,
            ColSpanMd = ColSpanMd(panel),
            ReadOnly = panel.IsCurrentlyReadOnly,
            Visible = panel.ActualVisible,
            Decoration = string.IsNullOrWhiteSpace(panel.Caption)
                ? FormLayoutGroupDecoration.None
                : FormLayoutGroupDecoration.Card,
            Items = CreateFormItems(panel.Items)
        };

        if (!string.IsNullOrWhiteSpace(panel.ImageName))
        {
            model.HeaderIconCssClass = "xaf-layout-group-icon";
            model.HeaderIconUrl = ImageUrlService.GetImageUrl(panel.ImageName);
        }

        if (panel.ShowCaption.HasValue && !panel.ShowCaption.Value)
        {
            model.Decoration = FormLayoutGroupDecoration.None;
        }

        if (model.Decoration == FormLayoutGroupDecoration.Card)
        {
            model.Expanded = panel.IsExpanded != false;
        }

        return ComponentModelObserver.Create(model, model.GetComponentContent());
    }

    private RenderFragment CreateField(Field field)
    {
        var caption = field.Caption;
        if (string.IsNullOrWhiteSpace(caption) && !string.IsNullOrWhiteSpace(field.Name))
        {
            caption = field.Name;
        }

        var model = new DxFormLayoutItemModel
        {
            Id = $"item-{field.Id}",
            Caption = caption,
            ColSpanMd = ColSpanMd(field),
            ReadOnly = field.IsCurrentlyReadOnly,
            Visible = field.ActualVisible,
            ChildContent = content => builder =>
            {
                builder.AddContent(0, CreateFieldComponent(field));
            }
        };

        var captionLocation = field.CaptionLocation ?? field.Form.CaptionLocation;
        if (captionLocation.HasValue)
        {
            model.CaptionPosition = captionLocation.Value switch
            {
                Locations.Left => CaptionPosition.Horizontal,
                Locations.Top => CaptionPosition.Vertical,
                _ => CaptionPosition.Horizontal,
            };
        }

        return ComponentModelObserver.Create(model, model.GetComponentContent());
    }

    private RenderFragment CreateFieldComponent(Field field)
    {
        var valueType = field.ValueType?.Trim()?.ToLowerInvariant();
        Type componentType = null;

        if (field is Table)
        {
            componentType = typeof(TableComponent);
        }
        else if (string.IsNullOrWhiteSpace(valueType) || valueType == "string")
        {
            componentType = typeof(TextBoxComponent);
        }
        else if (valueType == "int" || valueType == "long")
        {
            componentType = typeof(NumberComponent<int>);
        }
        else if (valueType == "float" || valueType == "double" || valueType == "number")
        {
            componentType = typeof(NumberComponent<double>);
        }
        else if (valueType == "bool")
        {
            componentType = typeof(CheckBoxComponent);
        }

        if (componentType != null)
        {
            return builder =>
            {
                builder.OpenComponent(0, componentType);
                builder.AddAttribute(1, nameof(FieldComponent.Field), field);
                builder.AddAttribute(2, nameof(FieldComponent.Data), FormDataDict);
                builder.AddAttribute(3, nameof(FieldComponent.ValueChanged), EventCallback.Factory.Create(
                    this, async () =>
                    {
                        UpdateFormStatus();
                        await FormDataChanged.InvokeAsync(FormData);
                    })
                );
                builder.CloseComponent();
            };
        }

        return null;
    }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenComponent<DxFormLayout>(0);
        builder.AddAttribute(1, nameof(DxFormLayout.ChildContent), (RenderFragment)(builder =>
        {
            builder.AddContent(0, CreateFormItems(Form.Items));
        }));
        builder.CloseComponent();
    }

    protected override async Task OnParametersSetAsync()
    {
        await base.OnParametersSetAsync();

        foreach (var field in Form.Fields)
        {
            if (!string.IsNullOrWhiteSpace(field.Name) && !FormDataDict.ContainsKey(field.Name))
            {
                FormData.TryAdd(field.Name, field is Table ? new List<ExpandoObject>() : null);
            }
        }

        UpdateFormStatus();
    }
}